using System;
using System.Collections.Generic;
using System.Text;
using SlimDX;
using Barrage;
using Barrage.Global;
using Spacedash.Logic.Scenario.Cells;
using System.IO;

namespace Spacedash.Logic.Scenario
{
    /// <summary>
    /// Stores cell data of the race scenario and functions to get additional data of positions etc.
    /// </summary>
    public class Scenario
    {
        /// <summary>
        /// Default scenario path
        /// </summary>
        public const string SCENARIO_DIR = "Scenarios/";
        /// <summary>
        /// Scenario file extension
        /// </summary>
        public const string SCENARIO_EXT = ".xml";
        
        /// <summary>
        /// Minimum length of an scenario (Since they're a loop, less than this value would be too short.
        /// </summary>
        public const int MIN_LENGTH = 50;
         string name;
        /// <summary>
        /// Scenario name
        /// </summary>
        public string Name { get { return name; }}

         Color3 color;
        /// <summary>
        /// Scenario base cell color
        /// </summary>
        public Color3 Color { get { return color; } }

        // ROW,COLUMN
         Cell[,] cells;

        /// <summary>
         /// List of cells that have been modified this frame
        /// </summary>
         List<Cell> changedCells = new List<Cell>();
        /// <summary>
        /// List of cells that have been modified this frame (for trail information)
        /// </summary>
        public List<Cell> ChangedCells { get { return changedCells; } }
        /// <summary>
        /// Adds a cell to the list of changed cells
        /// </summary>
        /// <param name="c">Cell to add to the list</param>
        public void AddChangedCell(Cell c)
        {
            changedCells.Add(c);
        }

        /// <summary>
        /// Clears the list of changed cells
        /// </summary>
        public void ClearChangedCells()
        {
            changedCells.Clear();
        }

         List<Cell> activeCells = new List<Cell>();
        /// <summary>
        /// Activates the cell (the cell will be updated on the next Update call)
        /// </summary>
        /// <param name="c">Cell to be activated</param>
        public void ActivateCell(Cell c)
        {
            c.Active = true;
            if (!activeCells.Contains(c))
                activeCells.Add(c);
        }

        /// <summary>
        /// Deactivate the cell (Removes it from the active cells list)
        /// </summary>
        /// <param name="c">Cell to be deactivated</param>
        public void DeactivateCell(Cell c)
        {
            activeCells.Remove(c);
            c.Active = false;
        }

        /// <summary>
        /// Number of columns of the scenario
        /// </summary>
        public int ColumnNumber { get { return cells.GetLength(1); }}

        /// <summary>
        /// Number of rows of the scenario
        /// </summary>
        public int RowNumber { get { return cells.GetLength(0); } }

        int gridLength = 1;
        /// <summary>
        /// Length of the starting grid in rows
        /// </summary>
        public int GridLength { get { return gridLength; } }

        /// <summary>
        /// Gets the column index associated to the 2D position specified
        /// </summary>
        /// <param name="position">2D position</param>
        /// <returns>Associated column index</returns>
        public int ColumnAt(Vector2 position)
        {
            return (int)Math.Round(position.X + ColumnNumber / 2);
        }

        /// <summary>
        /// Gets the row index associated to the 2D position specified
        /// </summary>
        /// <param name="position">2D position</param>
        /// <returns>Associated row index</returns>
        public int RowAt(Vector2 position)
        {
            return (int)Math.Truncate(position.Y) % RowNumber;
        }

        /// <summary>
        /// Returns the laps elapsed of an item placed at the 2D position specified
        /// It's based on the Y coordinate of the vector, dividing by the scenario row number
        /// </summary>
        /// <param name="position">2D position</param>
        /// <returns>Number of laps</returns>
        public int LapsElapsed(Vector2 position)
        {
            return (int)Math.Truncate(position.Y) / RowNumber;
        }

        /// <summary>
        /// Returns the cell at the specified position
        /// </summary>
        /// <param name="row">Row index</param>
        /// <param name="col">Column index</param>
        /// <returns>Cell at that position or null if the position isn't valid</returns>
        public Cell CellAt(int row,int col)
        {
            if ((row >= 0) && (row < RowNumber))
            {
                if ((col >= 0) && (col < ColumnNumber))
                {
                    Cell c = cells[row, col];
                    return c;
                }
            }
            return null;
        }

        /// <summary>
        /// Returns the cell at the specified position
        /// </summary>
        /// <param name="position">2D position</param>
        /// <returns>Cell at that position or null if the position isn't valid</returns>
        public Cell CellAt(Vector2 position)
        {
            int iRow = RowAt(position);

            if ((iRow >= 0) && (iRow < RowNumber))
            {
                int iCol = ColumnAt(position);
                
                if ((iCol >= 0) && (iCol < ColumnNumber))
                {
                    Cell c = cells[iRow,iCol];
                    return c;
                }
            }
            return null;
        }

        /// <summary>
        /// Length of the track in meters (RowNumber * CellSize.Y)
        /// </summary>
        public float TrackLength { get { return RowNumber * CellSize.Y; } }
        /// <summary>
        /// Radius of the track
        /// </summary>
        public float TrackRadius { get { return TrackLength / (2.0f * (float)Math.PI); } }
        /// <summary>
        /// Size of each tile (for UV data) (Currently [1,1])
        /// </summary>
        public Vector2 TileSize { get { return new Vector2(1, 1); } }
        /// <summary>
        /// Size of each cell in meters (Currently [10,20])
        /// </summary>
        public Vector2 CellSize { get { return new Vector2(10, 20); } }
        /// <summary>
        /// Texture used for the scenario's ground
        /// </summary>
        public string GroundTexture { get { return "Ground.png"; } }
        /// <summary>
        /// Lateral Radius of the scenario in meters
        /// </summary>
        public float LateralRadius { get { return CellSize.X * ColumnNumber / 2.0f; } }
        /// <summary>
        /// Max Side Angle the scenario can have
        /// </summary>
        public float MaxSideAngle { get { return (float)Math.PI / 2.5f; } }

        /// <summary>
        /// Returns the motion (position and rotation) correspondent to the specified 2D position
        /// </summary>
        /// <param name="position">2D position</param>
        /// <param name="normal">Normal of the ground at that position</param>
        /// <param name="tangent">Tangent of the ground at that position</param>
        /// <returns>Motion at that position</returns>
        public Motion getItemMotion(Vector2 position,out Vector3 normal, out Vector3 tangent)
        {
            Motion m =  new Motion();
            float Cos = -(float)Math.Cos((float)Math.PI * 2 * position.Y / RowNumber);
            float Sin = (float)Math.Sin((float)Math.PI * 2 * position.Y / RowNumber);
            float ZFactor = Sin;
            float YFactor = Cos;

            float latAngle = MaxSideAngle * (position.X / ColumnNumber) * 2; 
            float XFactor = (float)Math.Cos(latAngle - (float)Math.PI /2.0f) * LateralRadius;

            float Height = (float)Math.Sin(latAngle - (float)Math.PI / 2.0f) * LateralRadius + LateralRadius;
            YFactor *= TrackRadius;
            ZFactor *= TrackRadius;
            YFactor += -Cos * Height;
            ZFactor += -Sin * Height;
            normal = new Vector3(-XFactor, -YFactor, -ZFactor);
            normal.Normalize();

            tangent = Vector3.Cross(new Vector3(1, 0, 0), normal);
                   
            //XFactor = (float)(Math.Cos((position.X + 0.5) / HalfPipeness) + Math.Cos((position.X - 0.5) / HalfPipeness)) / 2.0f;
            //YFactor = XFactor;
            //ZFactor *= XFactor;
            //XFactor = (float)Math.Cos((position.X) / 2.0f);
            //XFactor = (float)Math.Cos((float)Math.PI * 1.75f * position.X / HalfPipeness);
            //XFactor = position.X > 0 ? 1-XFactor : -1+XFactor;
            m.Translation = new Vector3(XFactor, YFactor + TrackRadius, ZFactor);
            m.Rotation = Quaternion.RotationYawPitchRoll(0, -(float)Math.PI * 2 * position.Y / RowNumber, latAngle);
            return m;
        }

        /// <summary>
        /// Returns the motion (position and rotation) correspondent to the specified 2D position
        /// </summary>
        /// <param name="position">2D position</param>
        /// <returns>Motion at that position</returns>
        public Motion getItemMotion(Vector2 position)
        {
            Vector3 normal;
            Vector3 tangent;
            return getItemMotion(position, out normal,out tangent);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">Scenario name</param>
        /// <param name="rowNumber">Row number</param>
        /// <param name="columnNumber">Column number</param>
        /// <param name="gridLength">Starting grid Length</param>
        /// <param name="color">Cell color</param>
        public Scenario(string name, int rowNumber,int columnNumber, int gridLength, Color3 color)
        {
            cells = new Cell[rowNumber+gridLength, columnNumber];
            this.name = name;
            this.gridLength = gridLength;
            this.color = color;
            initCells();
        }

        void initCells()
        {
            for (int i = 0; i < RowNumber; i++)
                for (int j = 0; j < ColumnNumber; j++)
                {
                    Cell c = new Cell(i,j);
                    cells[i, j] = c;
                    if (i < GridLength)
                    {
                        c.Color = ((i + j) % 2 == 0) ? new Color3(0, 0, 0) : new Color3(1, 1, 1);
                        c.FixedColor = true;
                    }
                    else
                    {
                        c.BaseColor = Color;
                        c.Color = Color;
                    }
                    AddChangedCell(c);
                }
            
        }

        /// <summary>
        /// Returns a list of scenarios in the scenario folder
        /// </summary>
        /// <returns>List of scenario names</returns>
        public static List<string> GetAvailableScenarios()
        {
            List<string> names = new List<string>();
            DirectoryInfo di = new DirectoryInfo(SCENARIO_DIR);
            if (!di.Exists) return names;
            FileInfo[] files = di.GetFiles("*"+SCENARIO_EXT, SearchOption.TopDirectoryOnly);
            foreach (FileInfo fi in files)
            {
                string name = fi.Name;
                name = name.Substring(0, name.Length - 4);
                names.Add(name);
            }
            return names;
        }

        /// <summary>
        /// Updates every cell in the active list.
        /// After that, it removes the just deactivated cells from the list.
        /// </summary>
        public void UpdateCells()
        {
            List<Cell> deactivateCells=new List<Cell>();
            foreach (Cell c in activeCells)
            {
                c.Update();
                if (!c.Active)
                    deactivateCells.Add(c);
                AddChangedCell(c);
            }
            foreach (Cell c in deactivateCells)
            {
                activeCells.Remove(c);
            }
        }

    }
}
